// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.

#ifndef __ATMOSPHERE_FOG__
#define __ATMOSPHERE_FOG__

// Provides functions for atmospheric scattering and aerial perspective.
//
// Explanations:
// Scale Height = the altitude (height above ground) at which the average
//                atmospheric density is found.
// Optical Depth = also called optical length, airmass, etc.
//
// References:
// [GPUGems2] GPU Gems 2: Accurate Atmospheric Scattering by Sean O'Neil.
// [GPUPro3]  An Approximation to the Chapman Grazing-Incidence Function for
//            Atmospheric Scattering, GPU Pro3, pp. 105.
// Papers bei Bruneton, Nishita, etc.
//
// This code contains embedded portions of free sample source code from 
// http://www-evasion.imag.fr/Membres/Eric.Bruneton/PrecomputedAtmosphericScattering2.zip, Author: Eric Bruneton, 
// 08/16/2011, Copyright (c) 2008 INRIA, All Rights Reserved, which have been altered from their original version.

#include "./Flax/Atmosphere.hlsl"

static const float HeightOffset = 0.01f;

// inscattered light along ray x+tv, when sun in direction s (=S[L]-T(x,x0)S[L]|x0)
float3 GetInscatterColor(float fogDepth, float3 X, float T, float3 V, float3 S, float radius, float Mu, out float3 attenuation, bool isSceneGeometry)
{
	float3 result = float3(0.0f, 0.0f, 0.0f);
	attenuation = float3(1.0f, 1.0f, 1.0f);

	float d = -radius * Mu - sqrt(radius * radius * (Mu * Mu - 1.0) + RadiusAtmosphere * RadiusAtmosphere);
	if (d > 0.0f)
	{
		// if X in space and ray intersects atmosphere
		// move X to nearest intersection of ray with top atmosphere boundary
		X += d * V;
		T -= d;
		Mu = (radius * Mu + d) / RadiusAtmosphere;
		radius = RadiusAtmosphere;
	}

	float epsilon = 0.005f;

	if (radius < RadiusGround + HeightOffset + epsilon)
	{
		float diff = (RadiusGround + HeightOffset + epsilon) - radius;
		X -= diff * V;
		T -= diff;
		radius = RadiusGround + HeightOffset + epsilon;
		Mu = dot(X, V) / radius;
	}

	if (radius <= RadiusAtmosphere && fogDepth > 0.0f)
	{
		float3 X0 = X + T * V;
		float R0 = length(X0);
		float Nu = dot(V, S);
		float MuS = dot(X, S) / radius;

		float MuHorizon = -sqrt(1.0 - (RadiusGround / radius) * (RadiusGround / radius));

		if (isSceneGeometry)
		{
			Mu = max(Mu, MuHorizon + epsilon + 0.15f);
		}
		else
		{
			Mu = max(Mu, MuHorizon + epsilon);
		}

		float MuOriginal = Mu;
		float blendRatio = 0.0f;

		if (isSceneGeometry)
		{
			blendRatio = saturate(exp(-V.z) - 0.5);
			if (blendRatio < 1.0)
			{
				V.z = max(V.z, 0.15);
				V = normalize(V);
				float3 x1 = X + T * V;
				Mu = dot(x1, V) / length(x1);
			}
		}

		float phaseR = PhaseFunctionR(Nu);
		float phaseM = PhaseFunctionM(Nu);
		float4 inscatter = max(Texture4DSample(AtmosphereInscatterTexture, radius, Mu, MuS, Nu), 0.0);

		if (T > 0.0)
		{
			attenuation = AnalyticTransmittance(radius, Mu, T);
			float Mu0 = dot(X0, V) / R0;
			float MuS0 = dot(X0, S) / R0;
			if (isSceneGeometry)
			{
				R0 = max(R0, radius);
			}

			if (R0 > RadiusGround + HeightOffset)
			{
				if (blendRatio < 1.0)
				{
					inscatter = max(inscatter - attenuation.rgbr * Texture4DSample(AtmosphereInscatterTexture, R0, Mu0, MuS0, Nu), 0.0);
					if (!isSceneGeometry)
					{
						if (abs(Mu - MuHorizon) < epsilon)
						{
							Mu = MuHorizon - epsilon;
							R0 = sqrt(radius * radius + T * T + 2.0 * radius * T * Mu);
							Mu0 = (radius * Mu + T) / R0;

							Mu0 = max(MuHorizon + epsilon, Mu0);
							float4 inscatter0 = Texture4DSample(AtmosphereInscatterTexture, radius, Mu, MuS, Nu);
							float4 inscatter1 = Texture4DSample(AtmosphereInscatterTexture, R0, Mu0, MuS0, Nu);
							float4 inscatterA = max(inscatter0 - attenuation.rgbr * inscatter1, 0.0);

							Mu = MuHorizon + epsilon;
							R0 = sqrt(radius * radius + T * T + 2.0 * radius * T * Mu);

							Mu0 = (radius * Mu + T) / R0;
							Mu0 = max(MuHorizon + epsilon, Mu0);
							inscatter0 = Texture4DSample(AtmosphereInscatterTexture, radius, Mu, MuS, Nu);
							inscatter1 = Texture4DSample(AtmosphereInscatterTexture, R0, Mu0, MuS0, Nu);
							float4 inscatterB = max(inscatter1 - attenuation.rgbr * inscatter1, 0.0);

							float alpha = ((Mu - MuHorizon) + epsilon) * 0.5f / epsilon;
							inscatter = lerp(inscatterA, inscatterB, alpha);
						}
					}
					else if (blendRatio > 0.0)
					{
						inscatter = lerp(inscatter, (1.0 - attenuation.rgbr) * max(Texture4DSample(AtmosphereInscatterTexture, radius, MuOriginal, MuS, Nu), 0.0), blendRatio);
					}
				}
				else
				{
					inscatter = (1.0 - attenuation.rgbr) * inscatter;
				}
			}
		}

		inscatter.w *= smoothstep(0.00, 0.02, MuS);
		result = max(inscatter.rgb * phaseR + GetMie(inscatter) * phaseM, 0.0);
	}

	return result;
}

// Ground radiance at end of ray x+tv, when sun in direction s attenuated between ground and viewer (=R[L0]+R[L*])
float3 GetGroundColor(float4 sceneColor, float3 X, float T, float3 V, float3 S, float radius, float3 attenuation, bool isSceneGeometry)
{
	float3 result = float3(0.0f, 0.0f, 0.0f);
	if (T > 0.0f)
	{
		// if ray hits ground surface
		// ground Reflectance at end of ray, X0
		float3 X0 = X + T * V;
		float R0 = length(X0);
		float3 N = X0 / R0;
		sceneColor.xyz = saturate(sceneColor.xyz + 0.05);

		float4 reflectance = sceneColor * float4(0.2, 0.2, 0.2, 1.0);

		// direct sun light (radiance) reaching X0
		float MuS = dot(N, S);
		float3 sunLight = isSceneGeometry ? float3(0.f, 0.f, 0.f) : TransmittanceWithShadow(R0, MuS);

		// precomputed sky light (irradiance) (=E[L*]) at X0
		float3 groundSkyLight = Irradiance(AtmosphereIrradianceTexture, R0, MuS);

		// light reflected at X0 (=(R[L0]+R[L*])/T(X,X0))
		float3 groundColor = reflectance.rgb * ((max(MuS, 0.0) * sunLight + groundSkyLight) / PI);

		// water specular color due to SunLight
		if (!isSceneGeometry && reflectance.w > 0.0)
		{
			float3 H = normalize(S - V);
			float fresnel = 0.02 + 0.98 * pow(1.0 - dot(-V, H), 5.0);
			float waterBrdf = fresnel * pow(max(dot(H, N), 0.0), 150.0);
			groundColor += reflectance.w * max(waterBrdf, 0.0) * sunLight;
		}

		result = attenuation * groundColor; //=R[L0]+R[L*]
	}
	return result;
}

// Direct sun light for ray x+tv, when sun in direction s (=L0)
float3 GetSunColor(AtmosphericFogData atmosphericFog, float3 X, float T, float3 V, float3 S, float radius, float Mu)
{
	if (T > 0.0)
		return float3(0.0f, 0.0f, 0.0f);

	float3 transmittance = radius <= RadiusAtmosphere ? TransmittanceWithShadow(radius, Mu) : float3(1.0, 1.0, 1.0); // T(X,xo)
	float sunIntensity = step(cos(PI * atmosphericFog.AtmosphericFogSunDiscScale / 180.0), dot(V, S)); // Lsun
	return transmittance * sunIntensity; // Eq (9)
}

float3 inscatter(inout float3 x, inout float t, float3 v, float3 s, out float r, out float mu, out float3 attenuation)
{
	float3 result = 0;
	r = length(x);
	mu = dot(x, v) / r;
	float d = -r * mu - sqrt(r * r * (mu * mu - 1.0) + RadiusAtmosphere * RadiusAtmosphere);

	// if x in space and ray intersects atmosphere
	if (d > 0.0)
	{
		// move x to nearest intersection of ray with top atmosphere boundary
		x += d * v;
		t -= d;
		mu = (r * mu + d) / RadiusAtmosphere;
		r = RadiusAtmosphere;
	}

	float epsilon = 0.0045f;

	// if ray intersects atmosphere
	if (r <= RadiusAtmosphere)
	{
		float nu = dot(v, s);
		float muS = dot(x, s) / r;
		float phaseR = PhaseFunctionR(nu);
		float phaseM = PhaseFunctionM(nu);
		float4 inscatter = max(Texture4DSample(AtmosphereInscatterTexture, r, mu, muS, nu), 0.0);

		if (t > 0.0)
		{
			float3 x0 = x + t * v;
			float r0 = length(x0);
			float rMu0 = dot(x0, v);
			float mu0 = rMu0 / r0;
			float muS0 = dot(x0, s) / r0;
            attenuation = AnalyticTransmittance(r, mu, t);
			if (r0 > RadiusGround + 0.01)
			{
				// computes S[L]-T(x,x0)S[L]|x0
				inscatter = max(inscatter - attenuation.rgbr * Texture4DSample(AtmosphereInscatterTexture, r0, mu0, muS0, nu), 0.0);
                float muHoriz = -sqrt(1.0 - (RadiusGround / r) * (RadiusGround / r));
                if (abs(mu - muHoriz) < epsilon)
				{

                    mu = muHoriz - epsilon;
                    r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
                    mu0 = (r * mu + t) / r0;
                    float4 inScatter0 = Texture4DSample(AtmosphereInscatterTexture, r, mu, muS, nu);
                    float4 inScatter1 = Texture4DSample(AtmosphereInscatterTexture, r0, mu0, muS0, nu);
                    float4 inScatterA = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0);

                    mu = muHoriz + epsilon;
                    r0 = sqrt(r * r + t * t + 2.0 * r * t * mu);
                    mu0 = (r * mu + t) / r0;
                    inScatter0 = Texture4DSample(AtmosphereInscatterTexture, r, mu, muS, nu);
                    inScatter1 = Texture4DSample(AtmosphereInscatterTexture, r0, mu0, muS0, nu);
                    float4 inScatterB = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0);
					
                    float alpha = ((mu - muHoriz) + epsilon) / (2.0 * epsilon);
                    inscatter = lerp(inScatterA, inScatterB, alpha);
                }
			}
		}

        inscatter.w *= smoothstep(0.00, 0.02, muS);
		result = max(inscatter.rgb * phaseR + GetMie(inscatter) * phaseM, 0.0);
	}

	return result;
}

static const float EPSILON_ATMOSPHERE = 0.002f;
static const float EPSILON_INSCATTER = 0.004f;

// input - viewPosition: view position in world space
// input - d: view ray in world space
// output - offset: distance to atmosphere or 0 if within atmosphere
// output - maxPathLength: distance traversed within atmosphere
// output - return value: intersection occurred true/false
bool intersectAtmosphere(in float3 viewPosition, in float3 d, out float offset, out float maxPathLength)
{
	offset = 0.0f;
	maxPathLength = 0.0f;

	// vector from ray origin to center of the sphere
	float3 l = -viewPosition;
	float l2 = dot(l, l);
	float s = dot(l, d);

	// adjust top atmosphere boundary by small epsilon to prevent artifacts
	float r = Rt - EPSILON_ATMOSPHERE;
	float r2 = r * r;
	if (l2 <= r2)
	{
		// ray origin inside sphere, hit is ensured
		float m2 = l2 - (s * s);
		float q = sqrt(r2 - m2);
		maxPathLength = s + q;
		return true;
	}
	else if (s >= 0)
	{
		// ray starts outside in front of sphere, hit is possible
		float m2 = l2 - (s * s);
		if (m2 <= r2)
		{
			// ray hits atmosphere definitely
			float q = sqrt(r2 - m2);
			offset = s - q;
			maxPathLength = (s + q) - offset;
			return true;
		}
	}

	return false;
}

// input - surfacePos: reconstructed position of current pixel
// input - viewDir: view ray in world space
// input/output - attenuation: extinction factor along view path
// input/output - irradianceFactor: surface hit within atmosphere 1.0f, otherwise 0.0f
// output - return value: total in-scattered light
float3 GetInscatteredLight(AtmosphericFogData atmosphericFog, in float3 viewPosition, in float3 surfacePos, in float3 viewDir, in out float3 attenuation, in out float irradianceFactor)
{
	float3 inscatteredLight = float3(0.0f, 0.0f, 0.0f);
	float offset;
	float maxPathLength;

	if (intersectAtmosphere(viewPosition, viewDir, offset, maxPathLength))
	{
		return float3(offset / 10, 0, 0);

		float pathLength = distance(viewPosition, surfacePos);
		//return pathLength.xxx;

		// check if object occludes atmosphere
		if (pathLength > offset)
		{
			//return float3(1,0,0);

			// offsetting camera 
			float3 startPos = viewPosition + offset * viewDir;
			float startPosHeight = length(startPos);
			pathLength -= offset;

			// starting position of path is now ensured to be inside atmosphere
			// was either originally there or has been moved to top boundary
			float muStartPos = dot(startPos, viewDir) / startPosHeight;
			float nuStartPos = dot(viewDir, atmosphericFog.AtmosphericFogSunDirection);
			float musStartPos = dot(startPos, atmosphericFog.AtmosphericFogSunDirection) / startPosHeight;

			// in-scattering for infinite ray (light in-scattered when
			// no surface hit or object behind atmosphere)
			float4 inscatter = max(Texture4DSample(AtmosphereInscatterTexture, startPosHeight, muStartPos, musStartPos, nuStartPos), 0.0f);
			float surfacePosHeight = length(surfacePos);
			float musEndPos = dot(surfacePos, atmosphericFog.AtmosphericFogSunDirection) / surfacePosHeight;
			//return float3(muStartPos, 0, 0);

			// check if object hit is inside atmosphere
			if (pathLength < maxPathLength)
			{
				//return float3(1,0,0);

				// reduce total in-scattered light when surface hit
				// within atmosphere
				// fíx described in chapter 5.1.1
				attenuation = AnalyticTransmittance(startPosHeight, muStartPos, pathLength); // TODO: optimize this!! here
				float muEndPos = dot(surfacePos, viewDir) / surfacePosHeight;
				float4 inscatterSurface = Texture4DSample(AtmosphereInscatterTexture, surfacePosHeight, muEndPos, musEndPos, nuStartPos);
				inscatter = max(inscatter - attenuation.rgbr * inscatterSurface, 0.0f);
				irradianceFactor = 1.0f;
			}
			else
			{
				//return float3(1,0,0);

				// retrieve extinction factor for inifinte ray
				// fíx described in chapter 5.1.1
				attenuation = AnalyticTransmittance(startPosHeight, muStartPos, pathLength); // TODO: optimize this! and here
			}
			/*
			// avoids imprecision problems near horizon by interpolating between
			// two points above and below horizon
			// fíx described in chapter 5.1.2
			float muHorizon = -sqrt(1.0 - (g_Rg / startPosHeight) * (g_Rg / startPosHeight));
			if (abs(muStartPos - muHorizon) < EPSILON_INSCATTER)
			{
				float mu = muHorizon - EPSILON_INSCATTER;
				float samplePosHeight = sqrt(startPosHeight*startPosHeight + pathLength * pathLength + 2.0f * startPosHeight * pathLength*mu);
				float muSamplePos = (startPosHeight * mu + pathLength) / samplePosHeight;
				float4 inScatter0 = Texture4DSample(AtmosphereInscatterTexture, startPosHeight, mu, musStartPos, nuStartPos);
				float4 inScatter1 = Texture4DSample(AtmosphereInscatterTexture, samplePosHeight, muSamplePos, musEndPos, nuStartPos);
				float4 inScatterA = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0);
				mu = muHorizon + EPSILON_INSCATTER;
				samplePosHeight = sqrt(startPosHeight * startPosHeight + pathLength * pathLength + 2.0f * startPosHeight * pathLength * mu);
				muSamplePos = (startPosHeight * mu + pathLength) / samplePosHeight;
				inScatter0 = Texture4DSample(AtmosphereInscatterTexture, startPosHeight, mu, musStartPos, nuStartPos);
				inScatter1 = Texture4DSample(AtmosphereInscatterTexture, samplePosHeight, muSamplePos, musEndPos, nuStartPos);
				float4 inScatterB = max(inScatter0 - attenuation.rgbr * inScatter1, 0.0f);
				float t = ((muStartPos - muHorizon) + EPSILON_INSCATTER) / (2.0 * EPSILON_INSCATTER);
				inscatter = lerp(inScatterA, inScatterB, t);
			}
			*/

			// avoids imprecision problems in Mie scattering when sun is below
			//horizon
			// fíx described in chapter 5.1.3
			inscatter.w *= smoothstep(0.00, 0.02, musStartPos);
			float phaseR = PhaseFunctionR(nuStartPos);
			float phaseM = PhaseFunctionM(nuStartPos);
			inscatteredLight = max(inscatter.rgb * phaseR + GetMie(inscatter) * phaseM, 0.0f);

			float sunIntensity = 10;
			inscatteredLight *= sunIntensity;
		}
	}

	return inscatteredLight;
}

float4 GetAtmosphericFog(AtmosphericFogData atmosphericFog, float viewFar, float3 viewPosition, float3 viewVector, float sceneDepth, float3 sceneColor)
{
	float4 result = float4(1, 0, 0, 1);

#if 0
	
	float scale = 0.00001f * atmosphericFog.AtmosphericFogDistanceScale;// convert cm to km
	viewPosition.y = (viewPosition.y - atmosphericFog.AtmosphericFogGroundOffset) * atmosphericFog.AtmosphericFogAltitudeScale;
	//viewPosition *= atmosphericFog.AtmosphericFogDistanceScale;
	//viewPosition *= scale;

	//viewPosition *= scale;

	//if(length(worldPosition) > Rg)
	//	return float4(0, 0,0 ,0);
	
	float3 attenuation = float3(1.0f, 1.0f, 1.0f);
	float irradianceFactor = 0.0f;
	float3 inscatteredLight = GetInscatteredLight(atmosphericFog, viewPosition, worldPosition, viewVector, attenuation, irradianceFactor);
	//float3 inscatteredLight = GetInscatteredLight(atmosphericFog, worldPosition, viewPosition, viewVector, attenuation, irradianceFactor);
	
	return float4(inscatteredLight, 1);
	
	/*float offset = 0.0f;
	float maxPathLength = 0.0f;
	if(intersectAtmosphere(worldPosition, viewVector, offset, maxPathLength))
	{
		return float4(1,0,0,1);
	}*/
	
	return float4(0, 0, 0, 1);

#endif

#if 1

	// TODO: scale viewPosition from cm to km

	float scale = 0.0001f * atmosphericFog.AtmosphericFogDistanceScale;
	viewPosition.y = (viewPosition.y - atmosphericFog.AtmosphericFogGroundOffset) * atmosphericFog.AtmosphericFogAltitudeScale;
	//viewPosition *= atmosphericFog.AtmosphericFogDistanceScale;
	viewPosition *= scale;
	//viewPosition.xz *= 0.00001f;

	//viewPosition *= scale;
	viewPosition.y += RadiusGround + HeightOffset;

	//viewPosition.y -= atmosphericFog.AtmosphericFogGroundOffset;
	//worldPosition
	float Radius = length(viewPosition);
	float3 V = normalize(viewVector);
	float Mu = dot(viewPosition, V) / Radius;
	float T = -Radius * Mu - sqrt(Radius * Radius * (Mu * Mu - 1.0) + RadiusGround * RadiusGround);

	//-Radius * Mu > sqrt(Radius * Radius * (Mu * Mu - 1.0) + RadiusGround * RadiusGround)
	/*
	float3 g = viewPosition - float3(0.0, 0.0, RadiusGround + 10.0);
	float a = V.x * V.x + V.y * V.y - V.z * V.z;
	float b = 2.0 * (g.x * V.x + g.y * V.y - g.z * V.z);
	float c = g.x * g.x + g.y * g.y - g.z * g.z;
	float d = -(b + sqrt(b * b - 4.0 * a * c)) / (2.0 * a);
	bool cone = d > 0.0 && abs(viewPosition.z + d * V.z - RadiusGround) <= 10.0;
	
	if (T > 0.0)
	{
		if (cone && d < T)
			T = d;
		//return float4(1,0,0,1);
	}
	else if (cone)
	{
		T = d;
		//return float4(0,0,1,1);
	}
	*/
	//return float4(V, 1);

	// TODO: create uniform param for that depth limit
	float depthThreshold = min(100.0f * atmosphericFog.AtmosphericFogDistanceScale, (viewFar * 0.997f) * scale); // 100km limit or far plane
	sceneDepth *= scale;

	float fogDepth = max(0.0f, sceneDepth - atmosphericFog.AtmosphericFogStartDistance);
	float shadowFactor = 1.0f;
	float DistanceRatio = min(fogDepth * 0.1f / atmosphericFog.AtmosphericFogStartDistance, 1.0f);
	bool isSceneGeometry = (sceneDepth < depthThreshold); // Assume as scene geometry
	//isSceneGeometry = false;
	if (isSceneGeometry)
	{
		shadowFactor = DistanceRatio * atmosphericFog.AtmosphericFogPower;
		T = max(sceneDepth + atmosphericFog.AtmosphericFogDistanceOffset, 1.0f);

		// TODO: fog for scene objects
		return 0;
		//return float4(0, 1, 0, 1);
	}

	float3 attenuation;
	float3 inscatterColor = GetInscatterColor(fogDepth, viewPosition, T, V, atmosphericFog.AtmosphericFogSunDirection, Radius, Mu, attenuation, isSceneGeometry); //S[L]-T(viewPosition,xs)S[l]|xs
	//float3 inscatterColor = inscatter(viewPosition, T, V, atmosphericFog.AtmosphericFogSunDirection, Radius, Mu, attenuation); //S[L]-T(viewPosition,xs)S[l]|xs
	//float3 groundColor = 0;
	float3 groundColor = GetGroundColor(float4(sceneColor.rgb, 1.f), viewPosition, T, V, atmosphericFog.AtmosphericFogSunDirection, Radius, attenuation, isSceneGeometry); //R[L0]+R[L*]
	float3 sun = GetSunColor(atmosphericFog, viewPosition, T, V, atmosphericFog.AtmosphericFogSunDirection, Radius, Mu); //L0

	float3 color = (atmosphericFog.AtmosphericFogSunPower) * (sun + groundColor + inscatterColor) * atmosphericFog.AtmosphericFogSunColor.rgb;
	//return float4(color, lerp(saturate(attenuation.r * atmosphericFog.AtmosphericFogDensityScale - atmosphericFog.AtmosphericFogDensityOffset), 1.0f, (1.0f - DistanceRatio)) );
	//return lerp(saturate(attenuation.r * atmosphericFog.AtmosphericFogDensityScale - atmosphericFog.AtmosphericFogDensityOffset), 1.0f, (1.0f - DistanceRatio));
	//color *= lerp(saturate(attenuation.r * atmosphericFog.AtmosphericFogDensityScale - atmosphericFog.AtmosphericFogDensityOffset), 1.0f, (1.0f - DistanceRatio));

	//return attenuation.bbbb;

	return float4(color, 1);

	//return float4(sun, 1);
	//return float4(inscatterColor + sun, 1);
	//return float4(sun + groundColor, 1);
	//return float4(sun + groundColor + inscatterColor, 1);

#endif

	return result;
}

float4 GetAtmosphericFog(AtmosphericFogData atmosphericFog, float viewFar, float3 worldPosition, float3 cameraPosition)
{
	float3 viewVector = worldPosition - cameraPosition;
	return GetAtmosphericFog(atmosphericFog, viewFar, cameraPosition, viewVector, length(viewVector), float3(0, 0, 0));
}

#endif
